class Person {}
//@ts-ignore
Person.prototype.xxx = 123;

//如果要给每个类的原形上都添加这么一个属性呢?

//可以用一个函数
function addXxx(Person){
  Person.prototype.xxx = 123
}

//但这样不大好看

//So 我们就可以用语法糖把它编译一下咯


@addSpeak
class Person2 {
  /** 1. 使用装饰器为类添加原型方法 需要提前声明在类中才能点出来*/
  // ↓实例上的
  // public speak!: () => void;
  // ↓原型上的
  speak():void{}
  // 在这个场景中 效果一样

  /** 3. 为实例属性添加装饰器*/
  @toUpper(true)
  public name: string = 'ahhh';

  /** 4. 装饰函数*/
  @Enum(true)
  public drink(){
    console.log('inner drink');
  }
}

function addSpeak(target: any/*此时target是类*/){
  /** 1. 为类添加原型方法*/
  target.prototype.speak = function(){
    console.log('say');
  }

  /** 2. 为类添加静态属性*/
  target.type = '人'
}

const person = new Person2();

//默认点不出来我们@addSpeak添加的
// person.
//
//So 只能在Person2里先声明 (注意我们这里严格来说声明的是一个实例方法 但并不影响我们点出来 (类里,只要不是抽象类,ts是区分不了原型方法和实例方法的))
person.speak();

/** 2. 为类添加静态属性*/
namespace Person2 {
  export let type!: string
}
Person2.type


/** 3. 为实例属性添加装饰器*/
function toUpper(isUpper:boolean){
  return function (target: any/*此时target是类的原型*/, key: string) {
    let val = '';
    //给原形上添加属性
    Object.defineProperty(target, key, {
      get(){
        console.log('get--- --- ---');
        return isUpper ? val.toUpperCase() : val.toLowerCase();
      },
      //如果原型上通过defineProperty定义了同名属性, 给实例对应的属性赋值时, 会先走我们这里的set
      set(newVal){
        console.log('target === Person2.prototype:',target === Person2.prototype); //true
        console.log('set--- --- --- newVal:',newVal); //set--- --- --- newVal: ahhh
        val = newVal;
      }
    });
  };
}

console.log('person:',person)
console.log('person.name:',person.name); //AHHH



/** 4. 装饰函数*/
function Enum(isEnum: boolean){
  return function (target: any, key: string, descriptor: PropertyDescriptor/*修改成可枚举(即可遍历)、或者夹一层什么的*/) {
    descriptor.enumerable = isEnum;

    let oldDrink = descriptor.value; //被装饰器装饰的原本的那个函数
    descriptor.value = function(){
      console.log('outer drink');
      oldDrink();
    }
  };
}

console.log(Object.keys(Person2.prototype)); //[ 'drink' ]
person.drink();
/*
outer drink
inner drink
*/

export {};
